package com.slice.upload.strategy.biz;

import com.slice.upload.base.IStrategy;
import com.slice.upload.base.UploadMode;
import com.slice.upload.strategy.BaseSliceUploadStrategy;
import com.slice.upload.strategy.FileUploadPayload;
import lombok.extern.slf4j.Slf4j;
import sun.misc.Cleaner;

import java.io.File;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Objects;

/**
 * @ClassName MappedByteBufferUploadStrategy
 * @Description MappedByteBuffer 上传策略
 * @Author frinder
 * @Date 2021/1/27 17:36
 * @Verion 1.0
 */
@Slf4j
public class MappedByteBufferUploadStrategy extends BaseSliceUploadStrategy {

    @Override
    public void handle(FileUploadPayload payload, IStrategy strategy) {
        if (UploadMode.MAPPED.is(payload.getMode())) {
            try {
                // 写文件
                write(payload);
                // 设置上传结果
                setRespond(payload);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        } else {
            // 执行下一个策略
            strategy.handle(payload, strategy);
        }
    }

    /**
     * 写文件
     *
     * @param payload
     */
    private void write(FileUploadPayload payload) {
        File tmpFile = super.createTmpFile(payload);
        MappedByteBuffer buffer = null;
        try (
                RandomAccessFile file = new RandomAccessFile(tmpFile, "rw");
                FileChannel channel = file.getChannel()
        ) {
            long chunkSize = Objects.isNull(payload.getChunkFileSize()) ? defaultChunkSize : payload.getChunkFileSize();
            //写入该分片数据
            long offset = chunkSize * payload.getChunk();
            // 文件数据
            byte[] fileData = payload.getFile().getBytes();
            buffer = channel.map(FileChannel.MapMode.READ_WRITE, offset, fileData.length);
            buffer.put(fileData);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            freedMappedByteBuffer(buffer);
        }
    }

    /**
     * 在MappedByteBuffer释放后再对它进行读操作的话就会引发jvm crash，
     * 在并发情况下很容易发生 正在释放时另一个线程正开始读取，于是crash就发生了。
     * 所以为了系统稳定性释放前一般需要检查是否还有线程在读或写
     *
     * @param buffer
     */
    private void freedMappedByteBuffer(final MappedByteBuffer buffer) {
        try {
            if (buffer == null) {
                return;
            }
            buffer.force();
            AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                try {
                    Method getCleanerMethod = buffer.getClass().getMethod("cleaner", new Class[0]);
                    getCleanerMethod.setAccessible(true);
                    Cleaner cleaner = (Cleaner) getCleanerMethod.invoke(buffer, new Object[0]);
                    cleaner.clean();
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
                log.info("清空[MappedByteBuffer]完成");
                return null;
            });
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

}
